package com.nothing.service.impl;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.FaceSimilar;
import com.arcsoft.face.FunctionConfiguration;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.enums.ImageFormat;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.nothing.dao.UserFaceInfoDao;
import com.nothing.domain.UserFaceInfo;
import com.nothing.dto.FaceSearchResult;
import com.nothing.dto.FaceUserInfo;
import com.nothing.dto.ImageInfo;
import com.nothing.dto.ProcessInfo;
import com.nothing.hessian.config.HessionServiceConfig;
import com.nothing.service.FaceEngineService;
import com.nothing.util.ExcutorUtil;
import com.nothing.util.FaceEngineFactory;
import com.nothing.util.ImageUtil;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
/**
 * 迁移到后台
 * @author ShiQiang
 *
 */
@Deprecated
@Service
public class FaceEngineServiceImpl extends ServiceImpl<UserFaceInfoDao, UserFaceInfo> implements FaceEngineService {

	public final Logger logger = LoggerFactory.getLogger(this.getClass()); 
	
	@Autowired
	private UserFaceInfoDao userFaceInfoDao;

	private GenericObjectPool<FaceEngine> extractFaceObjectPool;
	private GenericObjectPool<FaceEngine> compareFaceObjectPool;

	private LoadingCache<Long, List<FaceUserInfo>> faceGroupCache;

	@PostConstruct
	public synchronized void init() {
		if(extractFaceObjectPool == null){
			initCache();
			ExcutorUtil.compareExecutorService = Executors.newFixedThreadPool(HessionServiceConfig.staticThreadPoolSize);
			GenericObjectPoolConfig<FaceEngine> poolConfig = new GenericObjectPoolConfig<>();
			poolConfig.setMaxIdle(HessionServiceConfig.staticThreadPoolSize);
			poolConfig.setMaxTotal(HessionServiceConfig.staticThreadPoolSize);
			poolConfig.setMinIdle(HessionServiceConfig.staticThreadPoolSize);
			poolConfig.setLifo(false);
			extractFaceObjectPool = new GenericObjectPool<>(
					new FaceEngineFactory(HessionServiceConfig.staticAppId, HessionServiceConfig.staticSdkKey, FunctionConfiguration.builder().supportFaceDetect(true)
							.supportFaceRecognition(true).supportAge(true).supportGender(true).build()),
					poolConfig);
			compareFaceObjectPool = new GenericObjectPool<>(new FaceEngineFactory(HessionServiceConfig.staticAppId, HessionServiceConfig.staticSdkKey,
					FunctionConfiguration.builder().supportFaceRecognition(true).build()), poolConfig);// 底层库算法对象池
		}
	}

	/**
	 * 初始化缓存
	 */
	public void initCache() {
		this.faceGroupCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(2, TimeUnit.HOURS)
				.build(new CacheLoader<Long, List<FaceUserInfo>>() {
					@Override
					public List<FaceUserInfo> load(Long groupId) throws Exception {
						List<FaceUserInfo> dbFacesIntoCache = Lists.newLinkedList();
						List<UserFaceInfo> userFaces = userFaceInfoDao.findByGroupId(groupId);
						if (CollectionUtil.isEmpty(userFaces))
							return null;

						for (UserFaceInfo k : userFaces) {
							dbFacesIntoCache.add(FaceUserInfo.builder().id(k.getId()).name(k.getName())
									.faceId(k.getFaceId()).faceFeature(k.getFaceFeature()).build());
						}
						return dbFacesIntoCache;
					}
				});

	}

	/** 乘以100 */
	private int plusHundred(Float value) {
		return new BigDecimal(value).multiply(new BigDecimal(100f)).intValue();
	}

	/** 特征提取引擎 */
	class ExtractEngine implements AutoCloseable {

		private FaceEngine engine;

		public FaceEngine get() throws Exception {
			return this.engine = extractFaceObjectPool.borrowObject();
		}

		@Override
		public void close() throws Exception {
			extractFaceObjectPool.returnObject(engine);
		}
	}

	/** 特征比较引擎 */
	class CompareEngine implements AutoCloseable {

		private FaceEngine engine;

		public FaceEngine get() throws Exception {
			return this.engine = compareFaceObjectPool.borrowObject();
		}

		@Override
		public void close() throws Exception {
			compareFaceObjectPool.returnObject(engine);
		}
	}

	@Override
	public void addFaceToCache(Long groupId, FaceUserInfo face) {
		try {
			List<FaceUserInfo> faceCaches = faceGroupCache.get(groupId);
			if (CollectionUtil.isEmpty(faceCaches)) {
				faceCaches = Lists.newArrayList();
				faceCaches.add(face);
				faceGroupCache.put(groupId, faceCaches);
			} else {
				faceCaches.add(face);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	//缓存中的数据，只有在没有使用过的时候才能删除，不然在扫描人脸的时候会有异常
	@Override
	public void delFaceFromCache(Long groupId, Long id) { 
		List<FaceUserInfo> faceCaches = faceGroupCache.getIfPresent(groupId);
		if (CollectionUtil.isEmpty(faceCaches))
			return;
		faceGroupCache.put(groupId,
				faceCaches.stream().filter(faceCache -> !faceCache.getId().equals(id)).collect(Collectors.toList()));

	}

	@Override
	public boolean whetherHasFaces(ImageInfo imageInfo) {
		FaceEngine engine = null;
		try {
			engine = extractFaceObjectPool.borrowObject();
			if (engine == null)
				return false;

			List<FaceInfo> facesInImg = Lists.newArrayList();
			engine.detectFaces(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg);

			if (CollectionUtil.isEmpty(facesInImg))
				return false;
			facesInImg.clear();

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (engine != null) {
				extractFaceObjectPool.returnObject(engine);
			}
		}
		return true;
	}

	@Override
	public List<FaceInfo> detectFaces(BufferedImage bufImage) {
		FaceEngine engine = null;
		try {
			engine = extractFaceObjectPool.borrowObject();
			if (engine == null)
				return null;
			ImageInfo imageInfo = ImageUtil.bufferedImage2ImageInfo(bufImage);

			List<FaceInfo> facesInImg = Lists.newArrayList();
			engine.detectFaces(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg);
			return CollectionUtil.isEmpty(facesInImg) ? null : facesInImg;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			if (engine != null) {
				extractFaceObjectPool.returnObject(engine);
			}
		}
	}

	@Override
	public List<FaceInfo> detectFaces(ImageInfo imageInfo) {
		FaceEngine engine = null;
		try {
			engine = extractFaceObjectPool.borrowObject();
			if (engine == null)
				return null;
			List<FaceInfo> facesInImg = Lists.newArrayList();
			engine.detectFaces(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg);

			return CollectionUtil.isEmpty(facesInImg) ? null : facesInImg;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			if (engine != null) {
				extractFaceObjectPool.returnObject(engine);
			}
		}
	}

	// 进行信息检测
	@Override
	public List<ProcessInfo> process(ImageInfo imageInfo) {

		FaceEngine faceEngine = null;
		try {
			faceEngine = extractFaceObjectPool.borrowObject();
			if (faceEngine == null)
				return null;
			// 人脸检测得到人脸列表
			List<FaceInfo> facesInImg = new ArrayList<FaceInfo>();
			// 人脸检测
			faceEngine.detectFaces(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg);
			faceEngine.process(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg,
					FunctionConfiguration.builder().supportAge(true).supportGender(true).build());
			List<ProcessInfo> processInfoList = Lists.newLinkedList();

			List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
			// 性别提取
			faceEngine.getGender(genderInfoList);
			// 年龄提取
			List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
			faceEngine.getAge(ageInfoList);
			for (int i = 0, total = genderInfoList.size(); i < total; i++) {
				ProcessInfo processInfo = new ProcessInfo();
				processInfo.setGender(genderInfoList.get(i).getGender());
				processInfo.setAge(ageInfoList.get(i).getAge());
				processInfo.setFace(facesInImg.get(i));
				processInfoList.add(processInfo);
			}
			return processInfoList;

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (faceEngine != null) {
				extractFaceObjectPool.returnObject(faceEngine);
			}
		}
		return null;
	}

	/**
	 * 人脸特征
	 */
	@Override
	public List<ProcessInfo> extractFaceFeature(ImageInfo imageInfo) throws InterruptedException {

		FaceEngine faceEngine = null;
		try {
			faceEngine = extractFaceObjectPool.borrowObject();
			if (faceEngine == null)
				return null;

			// 人脸检测得到人脸列表
			List<FaceInfo> facesInImg = new ArrayList<FaceInfo>();
			faceEngine.detectFaces(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg);
			if (CollectionUtil.isEmpty(facesInImg)) {
				return null;
			}

			faceEngine.process(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
					ImageFormat.CP_PAF_BGR24, facesInImg,
					FunctionConfiguration.builder().supportAge(true).supportGender(true).build());

			List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
			// 性别提取
			faceEngine.getGender(genderInfoList);
			// 年龄提取
			List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
			faceEngine.getAge(ageInfoList);

			List<ProcessInfo> features = Lists.newArrayList();
			if (CollectionUtil.isNotEmpty(facesInImg)) {
				for (int i = 0, total = facesInImg.size(); i < total; i++) {
					// 提取人脸特征
					FaceFeature faceFeature = new FaceFeature();
					faceEngine.extractFaceFeature(imageInfo.getRgbData(), imageInfo.getWidth(), imageInfo.getHeight(),
							ImageFormat.CP_PAF_BGR24, facesInImg.get(i), faceFeature);
					features.add(ProcessInfo.builder()
							.age(CollectionUtils.isEmpty(ageInfoList) || ageInfoList.get(i) == null ? 0
									: ageInfoList.get(i).getAge())
							.gender(CollectionUtils.isEmpty(genderInfoList) || genderInfoList.get(i) == null ? 0
									: genderInfoList.get(i).getGender())
							.face(facesInImg.get(i)).faceFeature(faceFeature).build());
				}
			}
			return features;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (faceEngine != null) {
				extractFaceObjectPool.returnObject(faceEngine);
			}
		}
		return null;
	}

	@Override
	public List<FaceUserInfo> compareFaceFeature(FaceFeature faceFeature, Long groupId) {

		List<FaceUserInfo> resultFaceInfoList = Lists.newLinkedList();// 识别到的人脸列表
		try {
			List<FaceUserInfo> facesInfoCache = faceGroupCache.get(groupId);// 从缓存中提取人脸库
			if (CollectionUtil.isEmpty(facesInfoCache))
				return null;

			List<List<FaceUserInfo>> facePartition = Lists.partition(facesInfoCache, 1000);// 分成1000一组，多线程处理
			CompletionService<List<FaceUserInfo>> completionService = new ExecutorCompletionService<>(ExcutorUtil.compareExecutorService);
			for (List<FaceUserInfo> part : facePartition) {

//				System.out.println("---------=====脸部特性对比======----------");
 
				completionService.submit(new CompareFaceTask(part, faceFeature));

			}
			for (int i = 0; i < facePartition.size(); i++) {
				List<FaceUserInfo> faceUserInfoList = completionService.take().get();

//				System.out.println("---------=====找到的人员======----------");

				if (CollectionUtil.isNotEmpty(faceUserInfoList)) {
					resultFaceInfoList.addAll(faceUserInfoList);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		resultFaceInfoList.sort((h1, h2) -> h2.getSimilarValue().compareTo(h1.getSimilarValue()));// 从大到小排序

		return resultFaceInfoList;
	}

	private class CompareFaceTask implements Callable<List<FaceUserInfo>> {

		private List<FaceUserInfo> faceUserInfoList;
		private FaceFeature targetFaceFeature;

		public CompareFaceTask(List<FaceUserInfo> faceUserInfoList, FaceFeature targetFaceFeature) {
			this.faceUserInfoList = faceUserInfoList;
			this.targetFaceFeature = targetFaceFeature;
		}

		@Override
		public List<FaceUserInfo> call() throws Exception {
			List<FaceUserInfo> callResult = Lists.newLinkedList();// 识别到的人脸列表
			FaceEngine faceEngine = null;
			try {
				faceEngine = compareFaceObjectPool.borrowObject();
				if (faceEngine == null)
					return null;

				for (FaceUserInfo faceUserInfo : faceUserInfoList) {

					FaceSimilar faceSimilar = new FaceSimilar(); 
					faceEngine.compareFaceFeature(targetFaceFeature, new FaceFeature(faceUserInfo.getFaceFeature()), faceSimilar);

					int similarValue = plusHundred(faceSimilar.getScore());// 获取相似值
					if (similarValue > ExcutorUtil.passRate) {// 相似值大于配置预期，加入到识别到人脸的列表
						callResult.add(faceUserInfo.setSimilarValue(similarValue));
						System.out.println("task线程：" + Thread.currentThread().getName() + "相似度=" + similarValue);
					} 
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (faceEngine != null) {
					compareFaceObjectPool.returnObject(faceEngine);
				}
			}
			return callResult;
		}
	}

	@Override
	public int faceSimilarScore(BufferedImage src, BufferedImage target){
		if(src == null || target == null){
			return 0;
		}
		FaceEngine faceEngine = null;
		try {
			faceEngine = compareFaceObjectPool.borrowObject();
			if (faceEngine == null)
				return 0;
			ImageInfo srcInfo = ImageUtil.bufferedImage2ImageInfo(src);
			ImageInfo targetInfo = ImageUtil.bufferedImage2ImageInfo(target);
			// 人脸特征获取
			List<ProcessInfo> srcFeatures = extractFacesFeature(srcInfo);
			List<ProcessInfo> targetFeatures = extractFacesFeature(targetInfo);
			if(CollectionUtil.isEmpty(srcFeatures)
					|| CollectionUtil.isEmpty(targetFeatures)){
				return 0;
			}
			
			FaceSimilar faceSimilar = new FaceSimilar();
			faceEngine.compareFaceFeature(srcFeatures.get(0).getFaceFeature(),targetFeatures.get(0).getFaceFeature(), faceSimilar);
			return plusHundred(faceSimilar.getScore());

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (faceEngine != null) {
				compareFaceObjectPool.returnObject(faceEngine);
			}
		}
		return 0;
	}

	@Override
	public List<FaceSearchResult> findUsersByFaces(BufferedImage bufImage, Long groupId) throws Exception {

		if (bufImage == null || groupId == null)
			return null;

		ImageInfo imageInfo = ImageUtil.bufferedImage2ImageInfo(bufImage);
		// 人脸特征获取
		List<ProcessInfo> features = extractFacesFeature(imageInfo);
		if (CollectionUtil.isEmpty(features))
			return null;

		return compareAndFindUser(features, groupId);
	}

	private List<FaceSearchResult> compareAndFindUser(List<ProcessInfo> features, Long groupId)
			throws InterruptedException, ExecutionException {

		List<FaceSearchResult> users = Lists.newArrayList();

		for (ProcessInfo process : features) {
			// 人脸比对，获取比对结果 只取相似度最高的那个
			List<FaceUserInfo> userFaceInfoList = compareFaceFeature(process.getFaceFeature(), groupId);

			if (CollectionUtil.isEmpty(userFaceInfoList))
				continue;

			FaceUserInfo faceUserInfo = userFaceInfoList.get(0);
			FaceSearchResult faceSearchResDto = new FaceSearchResult();
			BeanUtil.copyProperties(faceUserInfo, faceSearchResDto);
			users.add(faceSearchResDto);
		}

		return users;
	}

	private List<ProcessInfo> extractFacesFeature(ImageInfo imageInfo) {
		if(imageInfo == null)
			return null;
		FaceEngine faceEngine = null;
		try {
			faceEngine = extractFaceObjectPool.borrowObject();
			if (faceEngine == null)
				return null;
			// 人脸检测得到人脸列表
			List<FaceInfo> facesInImg = Lists.newArrayList();
			// 图片的长宽和数据
			final byte[] imgByts = imageInfo.getRgbData();
			final int width = imageInfo.getWidth();
			final int height = imageInfo.getHeight();
			//int code = 
			faceEngine.detectFaces(imgByts, width, height, ImageFormat.CP_PAF_BGR24, facesInImg);

			if (CollectionUtil.isEmpty(facesInImg))
				return null;

			List<ProcessInfo> features = Lists.newArrayList();
			for (FaceInfo face : facesInImg) {
				FaceFeature faceFeature = new FaceFeature();
				faceEngine.extractFaceFeature(imgByts, width, height, ImageFormat.CP_PAF_BGR24, face, faceFeature);
				if (faceFeature.getFeatureData() != null)
					features.add(ProcessInfo.builder().face(face).faceFeature(faceFeature).build());
			}

			return features;
		} catch (Exception e) {
			e.printStackTrace();

		} finally {
			if (faceEngine != null) {
				extractFaceObjectPool.returnObject(faceEngine);
			}
		}
		return null;
	}

	@Override
	public BufferedImage drowFace(BufferedImage bufImage) throws Exception {
		// 人脸特征获取
		List<FaceInfo> faces = detectFaces(bufImage);
		if (CollectionUtil.isEmpty(faces)) {
			return bufImage;
		}
		for (FaceInfo face : faces) {
			// 人脸比对，获取比对结果 只取相似度最高的那个 人脸检测
			int left = face.getRect().getLeft();
			int top = face.getRect().getTop();
			int width = face.getRect().getRight() - left;
			int height = face.getRect().getBottom() - top;
			drawRect(bufImage, left, top, width, height);
		}
		return bufImage;
	}

	@Override
	public BufferedImage drow(BufferedImage bufImage) throws Exception {

		ImageInfo imageInfo = ImageUtil.bufferedImage2ImageInfo(bufImage);
		// 人脸特征获取
		List<ProcessInfo> features = extractFaceFeature(imageInfo);
		if (CollectionUtil.isEmpty(features)) {
			return null;
		}

		for (ProcessInfo process : features) {
			// 人脸比对，获取比对结果 只取相似度最高的那个
			// 人脸检测
			int left = process.getFace().getRect().getLeft();
			int top = process.getFace().getRect().getTop();
			int width = process.getFace().getRect().getRight() - left;
			int height = process.getFace().getRect().getBottom() - top;
			drawRect(bufImage, left, top, width, height);
			drawPoint(bufImage, left + (width / 2), top + (height / 2), process.getFaceFeature().getFeatureData());
			drawAgeAndGender(bufImage, left, top, getIfNull(null, process.getAge()),
					getIfNull(null, process.getGender()));
		}
		return bufImage;
	}

	private Integer getIfNull(Integer a, Integer b) {
		return a != null ? a : ((b != null) ? b : 0);
	}

	/**
	 * 画正方形
	 */
	private void drawRect(BufferedImage bufImage, int left, int top, int width, int height) {
		Graphics2D graphics2D = bufImage.createGraphics();
		graphics2D.setColor(Color.RED);// 红色
		BasicStroke stroke = new BasicStroke(1f);
		graphics2D.setStroke(stroke);
		graphics2D.drawRect(left, top, width, height);
		graphics2D.dispose();
	}

	private void drawPoint(BufferedImage bufImage, int left, int top, byte[] bytes) {
		Graphics2D graphics2D = bufImage.createGraphics();
		graphics2D.setColor(Color.RED);// 红色
		BasicStroke stroke = new BasicStroke(1f);
		graphics2D.setStroke(stroke);
		int total = bytes.length;
		int[] xPoint = new int[total];
		int[] yPoint = new int[total];
		Map<Integer, List<Integer>> map = Maps.newHashMap();
		for (int i = 0; i < total / 2; i++) {
			// graphics2D.fillOval(left + bytes[2 * i],top + bytes[(2 * i ) +
			// 1], 2, 2);
			// graphics2D.drawLine(left + bytes[2 * i], top + bytes[(2 * i ) +
			// 1], left + bytes[(2 * i ) + 2], top + bytes[(2 * i ) + 3]);
			// 根据给定的多个点坐标绘制折线
			xPoint[i] = (left + bytes[2 * i] * 2);
			yPoint[i] = (top + bytes[(2 * i) + 1] * 2);
			if (map.containsKey(new Integer(bytes[2 * i]))) {
				List<Integer> yList = map.get(new Integer(bytes[2 * i]));
				yList.add(new Integer(bytes[(2 * i) + 1]));
			} else {
				List<Integer> yList = Lists.newArrayList();
				yList.add(new Integer(bytes[(2 * i) + 1]));
				map.put(new Integer(bytes[2 * i]), yList);
			}
		}
		map.forEach((key, value) -> {
			System.out.println("x:" + key + " | y:" + value);
		});
		graphics2D.drawPolyline(xPoint, yPoint, total);
		graphics2D.dispose();
	}

	private void drawAgeAndGender(BufferedImage bufImage, int left, int top, int age, int gender) {
		Graphics2D g = bufImage.createGraphics();
		g.setBackground(Color.white);
		g.drawImage(bufImage, 0, 0, null);
		Font font = new Font("微软雅黑", Font.PLAIN, 20);
		g.setFont(font);
		g.drawString("性别:" + (gender == 0 ? "男" : "女"), left + 100, top);
		g.drawString("年龄:" + age, left, top);
		g.dispose();
	}

}
